ItIron2023
react
我們昨天看了一個很水的custom hook題目,希望透過那個水到爆炸的題目你有了解到可以利用custom hook做類似的邏輯封裝提高你程式碼的可讀性與複用性,同時理解這並沒有這麼困難,不要被它嚇到了。今天我們來看一個稍稍複雜一些些的題目吧!
請你觀察這個codesandbox以及下方的starter code。
import React from "react";
import { fetchUsers } from "./api.js";
function App() {
return (
<div>
<h1>Implement Simple Pagination</h1>
<h2>User List</h2>
<button>Previous</button>
<button>Next</button>
</div>
);
}
export default App;
這次的題目要你實作一個簡單的pagination,如下方的gif所示。
題目已提供一個模擬api供你使用,fetchUsers函數會接受一個page做為參數,並回傳對應頁面的使用者資料以及是否有更多資料讓前端渲染。
// Mock API function
const fetchUsers = (page) => {
const allUsers = [
{ id: 1, name: "Alice", age: 28 },
{ id: 2, name: "Bob", age: 24 },
{ id: 3, name: "Charlie", age: 22 },
{ id: 4, name: "David", age: 35 },
{ id: 5, name: "Eva", age: 29 },
{ id: 6, name: "Frank", age: 41 },
{ id: 7, name: "Grace", age: 38 },
{ id: 8, name: "Hannah", age: 21 },
{ id: 9, name: "Ivy", age: 33 },
{ id: 10, name: "Jack", age: 30 },
{ id: 11, name: "Katie", age: 27 },
{ id: 12, name: "Liam", age: 32 },
{ id: 13, name: "Mandy", age: 26 },
{ id: 14, name: "Nancy", age: 19 },
{ id: 15, name: "Oscar", age: 45 },
{ id: 16, name: "Paul", age: 55 },
{ id: 17, name: "Quincy", age: 40 },
{ id: 18, name: "Rachel", age: 23 },
{ id: 19, name: "Steve", age: 34 },
{ id: 20, name: "Tina", age: 20 }
];
const startIdx = (page - 1) * 5;
const endIdx = startIdx + 5;
return new Promise((resolve) => {
setTimeout(() => {
const hasMore = endIdx < allUsers.length;
resolve({ users: allUsers.slice(startIdx, endIdx), hasMore });
}, 1000);
});
};
export { fetchUsers };
請觀察上方提供的程式碼以及gif檔案,並根據下方的要求完成題目
1. 請勿更動fetchUsers函數
2. 呈現的畫面應與gif一致,fetch資料時需要出現Loading字樣
3. Next & Previous按鈕需順利獲得下一頁/前一頁的使用者資料,且滿足特定條件時應disable對應的按鈕(第一頁不該能點擊Previous、最後一頁不該能點擊Next)
又是一個實務常見的需求,這類情境很容易出現在大量資料呈現的情況,為了避免一口氣渲染大量資料造成使用者體驗不佳,pagination或是infinite scrolling往往就是很直覺的解法,今天題目要求的內容很簡單,我們只要順利的渲染每一頁拿到的使用者資料,並根據api回傳的hasMore
變數來判斷是否有更多資料可以fetch來決定要不要disable下一頁的按鈕。
首先觀察一下幾個點,這會關係到我們需要哪些state來控制我們的渲染行為
因此第一步你會需要引入這四個state管理,並完成基本的render結構,坦白講做到這邊就已經快要結束了。
function App() {
const [users, setUsers] = useState([]);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(true);
return (
<div>
<h1>Implement Simple Pagination</h1>
<h2>User List</h2>
{loading ? (
<p>Loading...</p>
) : (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)}
<button>
Previous
</button>
<button>
Next
</button>
</div>
);
}
剩下的部分則是需要觀察一下api的回傳值,我們看一下api.js檔案我們會發現最後的resolve value如下。
resolve({ users: allUsers.slice(startIdx, endIdx), hasMore });
其中你並不需要去在乎allUsers跟startIdx & endIdx這些鬼玩意,你只要知道最終你會拿到users
& hasMore
這兩個欄位,這麼一來你就可以在請求後知道怎麼去設那些state了,加入請求資料的useEffect後我們就可以根據page的變化去請求對應的資料。
useEffect(() => { // 加入請求資料的effect
setLoading(true);
fetchUsers(page).then((data) => {
setUsers(data.users);
setLoading(false);
setHasMore(data.hasMore);
});
}, [page]);
到這邊之後就剩最後一步了,我們透過demo可以知道真正決定資料請求的邏輯其實是綁在兩個按鈕上,因此我們需要在兩個按鈕上掛上對應的onClick handler,點擊時需要根據情況更新page值讓上方的useEffect知道要重新fetch新的資料,同時你也要加入正確的邏輯讓按鈕在特定情況下是disabled的,最終完整的程式碼如下。
import React, { useState, useEffect } from "react";
import { fetchUsers } from "./api.js";
function App() {
const [users, setUsers] = useState([]);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(true);
useEffect(() => { // 加入請求資料的effect
setLoading(true);
fetchUsers(page).then((data) => {
setUsers(data.users);
setLoading(false);
setHasMore(data.hasMore);
});
}, [page]);
return (
<div>
<h1>Implement Simple Pagination</h1>
<h2>User List</h2>
{loading ? (
<p>Loading...</p>
) : (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)}
<button disabled={page === 1} onClick={() => setPage(page - 1)}>
Previous
</button>
<button disabled={!hasMore} onClick={() => setPage(page + 1)}>
Next
</button>
</div>
);
}
export default App;
今天我們看了一個簡單的pagination實作,雖然實務上的實作往往會包含更多的邏輯進去,但這個示範應該足夠讓你應付jr面試等級的問題了,做法上稱不上多困難,只要你有好好觀察題目的要求與提供的api,你大致上會很清楚需要哪一些state來達到你要的效果! 剩下最後五天了,加把勁吧! 我們明天見!
本文章同步發布於個人部落格,有興趣的朋友也可以來逛逛~!